package com.zdsoft.site;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.artofsolving.jodconverter.OfficeDocumentConverter;
import org.artofsolving.jodconverter.office.DefaultOfficeManagerConfiguration;
import org.artofsolving.jodconverter.office.OfficeException;
import org.artofsolving.jodconverter.office.OfficeManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 根据office模板生成pdf工具类
 * 
 * 所需配置文件:
 * 1,openoffice.org3.path 路径下必须要有 kill_2003.sh文件
 * 2,openoffice.org3.path 路径下必须要有 kill_2004.sh文件
 * 
 * 模板文件说明:
 * 
 * 1,map的value放入的是  单个对象时    模板写法如: {mapKey.obj1PropertyName.obj2PropertyName  ...}  
 * 									         将执行并返回map里key值等于mapKey的对象objPropertyName的get方法, 将无限调用深层对象 
 * 
 * 2,map的value放入的是  List<Object>时, 模板写法如：1,[mapKey.obj1PropertyName.obj2PropertyName.0]  从0开始向下循环  将无限调用深层对象
 * 											  2,[mapKey.index.0]  记录当前行序号
 * 3,模板的段落或表格中如非必要,请不要填写如  {非键值}  [非键值] 等带有大括号或中括号字样,避免异常情况无法解析。
 * 
 * 4,编辑模板文件时,如果敲击Enter键导致     “{”及“}” 或者  “[”及“]”不在同一行将会导致无法解析键值(word文档自动挤到下一行不影响)
 * 
 * @author xiapeng
 *
 */
public class ZDSPdfUtil {
	protected static final Logger logger=LoggerFactory.getLogger(ZDSPdfUtil.class);
	
	private static ZDSPdfUtil zdsPdfUtil = null;
	
	
	private SimpleDateFormat simpleDateFormat = null;
	
	private OfficeManager officeManager = null;
	
	private OfficeDocumentConverter officeConverter = null;
	
	private String officePort = null;
	
	private String officehome = null;
	
	
	private ZDSPdfUtil(){
		simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	}	
	
	public static ZDSPdfUtil getInstance(){
		if(zdsPdfUtil == null){
			zdsPdfUtil = new ZDSPdfUtil();
		}
		return zdsPdfUtil;
	}
	
	/**
	 * 【入口方法】
	 * 根据模板office文件生成PDF文件
	 * 工作流程:  根据模板office文件   -> 临时office文件 -> pdf文件 -> 删除临时office文件
	 * @param templateOfficePath  office模板文件绝对路径(含文件名)
	 * @param pdfPath             输出pdf文件绝对路径(含文件名)
	 * @param param               value值支持类型：1,BigDecimal
	 * 										    2,String
	 * 											3,Integer
	 * 											4,Date  默认格式: yyyy-MM-dd HH:mm:ss 
	 * 										    5,单个对象   如  productDto 《模板中写法见 类的头部说明》
	 * 										    6,List<Object>  如 List<productDto>	 《模板中写法见 类的头部说明》		
	 */
	public void templateOfficeToPdf(String templateOfficePath,String pdfPath,Map<String, Object> param){
		String tmpOfficePath = templateOfficePath.substring(0, templateOfficePath.lastIndexOf("."))+"_temp"
				+ templateOfficePath.substring(templateOfficePath.lastIndexOf("."));
		String tmp1OfficePath = tmpOfficePath.substring(0, tmpOfficePath.lastIndexOf("."))+"_1"
					+ tmpOfficePath.substring(tmpOfficePath.lastIndexOf("."));
		try {
			logger.debug("============开始生成合同==============================");
			
			logger.debug("模板office文件   => 临时office(1)文件 =>  临时office文件");
			templateOfficeToTmpOffice(templateOfficePath,tmp1OfficePath, tmpOfficePath, param);
			
			tmpOfficeToPdf(tmpOfficePath, pdfPath);
			
			logger.debug("删除 临时office(1)文件");
			deleteTmpFile(tmp1OfficePath);
			logger.debug("删除 临时office文件");
			deleteTmpFile(tmpOfficePath);
			
			logger.debug("============生产合同结束==============================");
		}catch(IOException e){
			logger.error("生成合同失败:{}",e);
			e.printStackTrace();
		}catch(OfficeException e){
			logger.error("生成合同失败:{}",e);
			e.printStackTrace();
		}catch(Exception e){
			logger.error("生成合同失败:{}",e);
			e.printStackTrace();
		}
	}
	/**
	 * 根据模板office文件   -> 临时office文件
	 * @param templateOfficePath
	 * @param tmpOfficePath
	 * @param param
	 * @throws IOException 
	 */
	private void templateOfficeToTmpOffice(String templateOfficePath,String tmp1OfficePath,String tmpOfficePath,Map<String, Object> param) throws IOException,Exception{
		
		 Map<String, String> replaceParamMap = new HashMap<String, String>();
		 
		 templateOfficeToTmp1Office(templateOfficePath,tmp1OfficePath,param,replaceParamMap);
		 
		 tmp1OfficeToTmpOffice(tmp1OfficePath,tmpOfficePath,param,replaceParamMap);
		
		 logger.debug("模板键值替换详细情况:{}",replaceParamMap.toString());
	}
	/**
	 * 删除临时文件
	 * @param filePath
	 */
	private void deleteTmpFile(String filePath){
		File file = new File(filePath);
		if(file.exists()){
			file.delete();
		}
	}
	/**
	 * 根据模板office文件   -> 临时office(1)文件
	 * @param templateOfficePath
	 * @param tmp1OfficePath
	 * @param param
	 * @param replaceParamMap
	 * @throws IOException
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	private void templateOfficeToTmp1Office(String templateOfficePath,String tmp1OfficePath,Map<String, Object> param,
				Map<String, String> replaceParamMap) 
				throws IOException,Exception{
		 OPCPackage opcPackage = POIXMLDocument.openPackage(templateOfficePath);
		 XWPFDocument doc = new XWPFDocument(opcPackage);  
		 insertValueToParagraphs(doc.getParagraphs(),param,replaceParamMap,false,null);
		 
		 List<XWPFTable> tables = doc.getTables();
		 
		 for (int i = 0; i < tables.size(); i++) {
			XWPFTable table = tables.get(i);
			
			List<XWPFTableRow> rows = table.getRows();
			for (int j = 0; j < rows.size(); j++) {
				XWPFTableRow row = rows.get(j);
				List<XWPFTableCell> cells = row.getTableCells();
				
				for (int k = 0; k < cells.size(); k++) {
					XWPFTableCell cell = cells.get(k);
					List<String> needReplaceList = new ArrayList<String>();
					matcheNeedReplaceList(cell.getText().trim(),needReplaceList,true);
					
					if(needReplaceList.size() > 0){
						String prefixReplaceString = needReplaceList.get(0).substring(0,needReplaceList.get(0).lastIndexOf("."));
						if(param.get(prefixReplaceString.split("\\.")[0]) == null 
								|| ((List<Object>)param.get(prefixReplaceString.split("\\.")[0])).size() == 0){
							table.removeRow(j);
							j = j - 1;
						}else{
							int needAddNewrowsSize = ((List<Object>)param.get(prefixReplaceString.split("\\.")[0])).size() - 1;
							for (int l = 0; l < needAddNewrowsSize; l++) {
								table.addRow(row, j+1);
							}
							j = j + needAddNewrowsSize;
						}
						break;
					}
				}
				
			}
		}
	
		OutputStream os = new FileOutputStream(tmp1OfficePath);
	    doc.write(os);
		os.close();
		doc.close();
	}
	
	
	/**
	 *  临时office(1)文件 -> 临时office文件
	 * @param tmp1OfficePath
	 * @param tmpOfficePath
	 * @param param
	 * @param replaceParamMap
	 * @throws IOException
	 * @throws Exception
	 */
	private void tmp1OfficeToTmpOffice(String tmp1OfficePath,String tmpOfficePath,Map<String, Object> param,
			Map<String, String> replaceParamMap) throws IOException,Exception{
		 OPCPackage opcPackage = POIXMLDocument.openPackage(tmp1OfficePath);
		 XWPFDocument doc = new XWPFDocument(opcPackage);  
		 List<XWPFTable> tables = doc.getTables();
		 for (int i = 0; i < tables.size(); i++) {
				XWPFTable table = tables.get(i);
				
				List<XWPFTableRow> rows = table.getRows();
				for (int j = 0; j < rows.size(); j++) {
					XWPFTableRow row = rows.get(j);
					List<XWPFTableCell> cells = row.getTableCells();
					
					for (int k = 0; k < cells.size(); k++) {
						XWPFTableCell cell = cells.get(k);
						List<String> needReplaceList = new ArrayList<String>();
						matcheNeedReplaceList(cell.getText().trim(),needReplaceList,true);
						
						if(needReplaceList.size() > 0){
							String prefixReplaceString = needReplaceList.get(0).substring(0,needReplaceList.get(0).lastIndexOf("."));
							String listIndex =  needReplaceList.get(0).substring(needReplaceList.get(0).lastIndexOf(".")+1);
							if(j+1 < rows.size() && rows.get(j+1).getTableCells().size() == cells.size()){
								insertValueToParagraphs(rows.get(j+1).getCell(k).getParagraphs(), param, 
										replaceParamMap, true,"["+prefixReplaceString+"."+(Integer.valueOf(listIndex).intValue()+1)+"]");
							}
							insertValueToParagraphs(cell.getParagraphs(), param, replaceParamMap, true,null);
						}else{
							insertValueToParagraphs(cell.getParagraphs(), param, replaceParamMap, false,null);
						}
					}
					
				}
			}
		
		OutputStream os = new FileOutputStream(tmpOfficePath);
	    doc.write(os);
		os.close();
		doc.close();
		opcPackage.close();
	}
	
	/**
	 * 临时office文件 -> pdf文件 
	 * @param tmpOfficePath
	 * @param pdfPath
	 */
	private void tmpOfficeToPdf(String tmpOfficePath,String pdfPath) throws IOException,OfficeException{
		File pdfFile = new File(pdfPath);
		File tmpOfficeFile = new File(tmpOfficePath);
		if(!pdfFile.getParentFile().exists()){
			pdfFile.getParentFile().mkdirs();
		}
		if(officeConverter == null){
			startOffice();
			officeConverter = new OfficeDocumentConverter(
					officeManager);
		}
		logger.debug("临时office文件 -> pdf文件:{}",pdfPath);
		try {
			officeConverter.convert(tmpOfficeFile, pdfFile);
		} catch (Exception e) {
			logger.debug("临时office文件 -> pdf文件 转换异常,启动补救机制...");
			officeManager = null;
			startOffice();
			officeConverter = new OfficeDocumentConverter(
					officeManager);
			logger.debug("开始重新转换:临时office文件 -> pdf文件:{}",pdfPath);
			officeConverter.convert(tmpOfficeFile, pdfFile);
		}
	}
	/**
	 * 匹配需要替换的list集合
	 * @param str
	 * @param list
	 */
	private void matcheNeedReplaceList(String str,List<String> list,boolean isList){
		String regex = isList?"^\\[(.+?)\\]$":".*?\\{(.+?)\\}";
		Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
		Matcher matchers = pattern.matcher(str);
		while (matchers.find()) {
			String tmpStr = matchers.group(1);
			//列表中包含字符串
			if(list.contains(tmpStr)){
				continue;
			}
			//没找到点号
			if(isList && tmpStr.indexOf(".") == -1){
				continue;
			}
			//找到点号,且点号后的不是数字
			if(isList && tmpStr.indexOf(".") > 0 && !StringUtils.isNumeric(tmpStr.substring(tmpStr.lastIndexOf(".")+1))){
				continue;
			}
			list.add(tmpStr);
		}
	}
	
	
	/**
	 * 替换param -> replaceParamMap
	 * @param replaceString
	 * @param param
	 * @param replaceParamMap
	 * @param isList 是否是list集合
	 * @param replaceValue 固定替换值
	 */
	@SuppressWarnings("unchecked")
	private String setReplaceParamMap(String replaceString,Map<String, Object> param,Map<String, String> replaceParamMap,
			boolean isList,String replaceValue)
			throws Exception{
		if(!StringUtils.isEmpty(replaceValue)){
			return replaceValue;
		}
		if(replaceParamMap.containsKey(replaceString)){
			return replaceParamMap.get(replaceString);
		}
		String tmpReplaceString = replaceString;
		Integer listIndex = null;
		if(isList){
			tmpReplaceString = replaceString.substring(0, replaceString.lastIndexOf("."));
			listIndex = Integer.valueOf(replaceString.substring(replaceString.lastIndexOf(".")+1));
		}
		
		String[] replaceStringArr = tmpReplaceString.split("\\.");
		if(replaceStringArr.length == 2 && replaceStringArr[1].equals("index")){
			return listIndex.toString();
		}
		Object obj = null;
		for (int i = 0; i < replaceStringArr.length; i++) {
			String tmpStr = replaceStringArr[i];
			if(i == 0 && !param.containsKey(tmpStr)){
				return isList?"["+replaceString+"]":"{"+replaceString+"}";
			}
			if(i == 0 && param.containsKey(tmpStr)){
				obj = isList && param.get(tmpStr) != null 
						&& ((List<Object>)param.get(tmpStr)).size() > listIndex.intValue()
						?((List<Object>)param.get(tmpStr)).get(listIndex.intValue()):param.get(tmpStr);
			}else{
				obj = obj.getClass()
						.getMethod("get" + tmpStr.replaceFirst(tmpStr.substring(0, 1),tmpStr.substring(0, 1).toUpperCase()))
						.invoke(obj);
			}
			
			if(obj == null){
				replaceParamMap.put(replaceString, "");
				return "";
			}
			if((obj instanceof String) 
					|| (obj instanceof BigDecimal)
					|| (obj instanceof Integer)
					){
				replaceParamMap.put(replaceString, obj.toString());
				return obj.toString();
			}
			if(obj instanceof Date){
				replaceParamMap.put(replaceString, simpleDateFormat.format((Date)obj));
				return simpleDateFormat.format((Date)obj);
			}
			
		}
		replaceParamMap.put(replaceString, "");
		return ""; 
	}
	/**
	 * 动态   替换 XWPFParagraph 集合类型值
	 * @param paragraphs
	 * @param param
	 * @param replaceParamMap
	 * @param isList 是否list集合
	 * @param replaceValue 固定替换值
	 * @throws Exception 
	 */
	private void insertValueToParagraphs(List<XWPFParagraph> paragraphs,Map<String, Object> param,
			Map<String, String> replaceParamMap,boolean isList,String replaceValue) throws Exception{
		 for (int i = 0; i < paragraphs.size(); i++) {
			 XWPFParagraph paragraph = paragraphs.get(i);
			 List<XWPFRun> runs = paragraph.getRuns();
			
			 for (int j = 0; j < runs.size(); j++) {
				 XWPFRun run = runs.get(j);
				 String tmpRunStr = run.toString().trim();
				 List<String> needReplaceList = new ArrayList<String>();
				 String prefixBrackets = isList?"[":"{";
				 String suffixBrackets = isList?"]":"}";
				 matcheNeedReplaceList(tmpRunStr,needReplaceList,isList);
				 
				 boolean isDoReplace = false;
				 for (String replaceString : needReplaceList) {
					 isDoReplace = true;
					 tmpRunStr = tmpRunStr.replace(prefixBrackets+replaceString+suffixBrackets, setReplaceParamMap(replaceString,param,replaceParamMap,isList,replaceValue));
				 }
				 
				 if(tmpRunStr.lastIndexOf(prefixBrackets) >= 0 && tmpRunStr.lastIndexOf(prefixBrackets) > tmpRunStr.lastIndexOf(suffixBrackets)){
					isDoReplace = true;
					StringBuffer nextStringBuf = new StringBuffer();
					for (int k = j+1; k < runs.size();) {
						XWPFRun nextRun = runs.get(k);
						String nextTmpRunStr = nextRun.toString();
						if(nextTmpRunStr.indexOf(suffixBrackets) == -1){
							nextStringBuf.append(nextTmpRunStr);
							paragraph.removeRun(k);
						}else{
							nextStringBuf.append(nextTmpRunStr.substring(0, nextTmpRunStr.indexOf(suffixBrackets)));
							nextRun.setText(nextTmpRunStr.substring(nextTmpRunStr.indexOf(suffixBrackets)+1), 0);
							break;
						}
					} 
					String replaceString = tmpRunStr.substring(tmpRunStr.lastIndexOf(prefixBrackets)+1) + nextStringBuf.toString();
					tmpRunStr = tmpRunStr.substring(0, tmpRunStr.lastIndexOf(prefixBrackets));	
					tmpRunStr = tmpRunStr + setReplaceParamMap(replaceString,param,replaceParamMap,isList,replaceValue);
				 }
				 if(isDoReplace){
					 run.setText(tmpRunStr, 0);
				 }
			}
			 
		}
	}
	
	
	/**
	 * 启动office服务
	 * @throws OfficeException
	 * @throws IOException 
	 */
	public OfficeManager startOffice() throws OfficeException, IOException{
		if(officeManager == null){
			killOpenOfficeProcess();
			
			DefaultOfficeManagerConfiguration config = new DefaultOfficeManagerConfiguration();
			config.setOfficeHome(getOfficehome());
			config.setPortNumber(Integer.valueOf(getOfficePort()).intValue());
			officeManager = config.buildOfficeManager();
			
			logger.debug("开始启动opentOffice服务...");
			officeManager.start();
			logger.debug("opentOffice服务启动成功");
		}
		return officeManager;
	}
	/**
	 * 获取openoffice安装路径
	 * @return
	 */
	private String getOfficehome(){
		if(officehome == null){
			//officehome = CacheUtil.getProperty("openoffice.org3.path")+File.separator;
		}
		return officehome;
	}
	/**
	 * 获取office所在端口号
	 * @return
	 */
	private String getOfficePort(){
		if(officePort == null){
			String projectName = ZDSPdfUtil.class.getClassLoader().getResource("").toString();
			projectName = projectName.substring(0,projectName.indexOf("/WEB-INF"));
			projectName = projectName.substring(projectName.lastIndexOf("/"));
			String webApiUrl = "";//CacheUtil.getProperty("webApi.root.url");
			String backUrl = "";//CacheUtil.getProperty("back.home.url");
			if(webApiUrl.indexOf(projectName) >= 0){
				officePort = "2003";
			}
			if(backUrl.indexOf(projectName) >= 0){
				officePort = "2004";
			}
		}
		return officePort;
	}
	/**
	 * 杀死服务器已经存在的openoffice进程,防止端口占用异常
	 * @throws IOException 
	 */
	private void killOpenOfficeProcess() throws IOException{
		String execString = getOfficehome() + "kill_" + getOfficePort() + ".sh";
		Runtime.getRuntime().exec(execString);
	}
	
}
